home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / PEAR / Common.php < prev    next >
PHP Script  |  2004-10-01  |  69KB  |  2,038 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at the following url:           |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@php.net>                                   |
  17. // |          Tomas V.V.Cox <cox@idecnet.com>                             |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: Common.php,v 1.121 2004/03/12 18:22:48 pajoye Exp $
  21.  
  22. require_once 'PEAR.php';
  23. require_once 'Archive/Tar.php';
  24. require_once 'System.php';
  25. require_once 'PEAR/Config.php';
  26.  
  27. // {{{ constants and globals
  28.  
  29. /**
  30.  * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode()
  31.  */
  32. define('PEAR_COMMON_ERROR_INVALIDPHP', 1);
  33. define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+');
  34. define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '$/');
  35.  
  36. // this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1
  37. define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-z]+\d*)?');
  38. define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '$/i');
  39.  
  40. // XXX far from perfect :-)
  41. define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^(' . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?$/');
  42.  
  43. /**
  44.  * List of temporary files and directories registered by
  45.  * PEAR_Common::addTempFile().
  46.  * @var array
  47.  */
  48. $GLOBALS['_PEAR_Common_tempfiles'] = array();
  49.  
  50. /**
  51.  * Valid maintainer roles
  52.  * @var array
  53.  */
  54. $GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper');
  55.  
  56. /**
  57.  * Valid release states
  58.  * @var array
  59.  */
  60. $GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel');
  61.  
  62. /**
  63.  * Valid dependency types
  64.  * @var array
  65.  */
  66. $GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
  67.  
  68. /**
  69.  * Valid dependency relations
  70.  * @var array
  71.  */
  72. $GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not');
  73.  
  74. /**
  75.  * Valid file roles
  76.  * @var array
  77.  */
  78. $GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script');
  79.  
  80. /**
  81.  * Valid replacement types
  82.  * @var array
  83.  */
  84. $GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info');
  85.  
  86. /**
  87.  * Valid "provide" types
  88.  * @var array
  89.  */
  90. $GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api');
  91.  
  92. /**
  93.  * Valid "provide" types
  94.  * @var array
  95.  */
  96. $GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup');
  97.  
  98. // }}}
  99.  
  100. /**
  101.  * Class providing common functionality for PEAR adminsitration classes.
  102.  */
  103. class PEAR_Common extends PEAR
  104. {
  105.     // {{{ properties
  106.  
  107.     /** stack of elements, gives some sort of XML context */
  108.     var $element_stack = array();
  109.  
  110.     /** name of currently parsed XML element */
  111.     var $current_element;
  112.  
  113.     /** array of attributes of the currently parsed XML element */
  114.     var $current_attributes = array();
  115.  
  116.     /** assoc with information about a package */
  117.     var $pkginfo = array();
  118.  
  119.     /**
  120.      * User Interface object (PEAR_Frontend_* class).  If null,
  121.      * the log() method uses print.
  122.      * @var object
  123.      */
  124.     var $ui = null;
  125.  
  126.     /**
  127.      * Configuration object (PEAR_Config).
  128.      * @var object
  129.      */
  130.     var $config = null;
  131.  
  132.     var $current_path = null;
  133.  
  134.     /**
  135.      * PEAR_SourceAnalyzer instance
  136.      * @var object
  137.      */
  138.     var $source_analyzer = null;
  139.     /**
  140.      * Flag variable used to mark a valid package file
  141.      * @var boolean
  142.      * @access private
  143.      */
  144.     var $_validPackageFile;
  145.     /**
  146.      * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
  147.      * @var array
  148.      * @access private
  149.      */
  150.     var $_packageSortTree;
  151.  
  152.     // }}}
  153.  
  154.     // {{{ constructor
  155.  
  156.     /**
  157.      * PEAR_Common constructor
  158.      *
  159.      * @access public
  160.      */
  161.     function PEAR_Common()
  162.     {
  163.         parent::PEAR();
  164.         $this->config = &PEAR_Config::singleton();
  165.         $this->debug = $this->config->get('verbose');
  166.     }
  167.  
  168.     // }}}
  169.     // {{{ destructor
  170.  
  171.     /**
  172.      * PEAR_Common destructor
  173.      *
  174.      * @access private
  175.      */
  176.     function _PEAR_Common()
  177.     {
  178.         // doesn't work due to bug #14744
  179.         //$tempfiles = $this->_tempfiles;
  180.         $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles'];
  181.         while ($file = array_shift($tempfiles)) {
  182.             if (@is_dir($file)) {
  183.                 System::rm(array('-rf', $file));
  184.             } elseif (file_exists($file)) {
  185.                 unlink($file);
  186.             }
  187.         }
  188.     }
  189.  
  190.     // }}}
  191.     // {{{ addTempFile()
  192.  
  193.     /**
  194.      * Register a temporary file or directory.  When the destructor is
  195.      * executed, all registered temporary files and directories are
  196.      * removed.
  197.      *
  198.      * @param string  $file  name of file or directory
  199.      *
  200.      * @return void
  201.      *
  202.      * @access public
  203.      */
  204.     function addTempFile($file)
  205.     {
  206.         $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
  207.     }
  208.  
  209.     // }}}
  210.     // {{{ mkDirHier()
  211.  
  212.     /**
  213.      * Wrapper to System::mkDir(), creates a directory as well as
  214.      * any necessary parent directories.
  215.      *
  216.      * @param string  $dir  directory name
  217.      *
  218.      * @return bool TRUE on success, or a PEAR error
  219.      *
  220.      * @access public
  221.      */
  222.     function mkDirHier($dir)
  223.     {
  224.         $this->log(2, "+ create dir $dir");
  225.         return System::mkDir(array('-p', $dir));
  226.     }
  227.  
  228.     // }}}
  229.     // {{{ log()
  230.  
  231.     /**
  232.      * Logging method.
  233.      *
  234.      * @param int    $level  log level (0 is quiet, higher is noisier)
  235.      * @param string $msg    message to write to the log
  236.      *
  237.      * @return void
  238.      *
  239.      * @access public
  240.      */
  241.     function log($level, $msg, $append_crlf = true)
  242.     {
  243.         if ($this->debug >= $level) {
  244.             if (is_object($this->ui)) {
  245.                 $this->ui->log($msg, $append_crlf);
  246.             } else {
  247.                 print "$msg\n";
  248.             }
  249.         }
  250.     }
  251.  
  252.     // }}}
  253.     // {{{ mkTempDir()
  254.  
  255.     /**
  256.      * Create and register a temporary directory.
  257.      *
  258.      * @param string $tmpdir (optional) Directory to use as tmpdir.
  259.      *                       Will use system defaults (for example
  260.      *                       /tmp or c:\windows\temp) if not specified
  261.      *
  262.      * @return string name of created directory
  263.      *
  264.      * @access public
  265.      */
  266.     function mkTempDir($tmpdir = '')
  267.     {
  268.         if ($tmpdir) {
  269.             $topt = array('-t', $tmpdir);
  270.         } else {
  271.             $topt = array();
  272.         }
  273.         $topt = array_merge($topt, array('-d', 'pear'));
  274.         if (!$tmpdir = System::mktemp($topt)) {
  275.             return false;
  276.         }
  277.         $this->addTempFile($tmpdir);
  278.         return $tmpdir;
  279.     }
  280.  
  281.     // }}}
  282.     // {{{ setFrontendObject()
  283.  
  284.     /**
  285.      * Set object that represents the frontend to be used.
  286.      *
  287.      * @param  object Reference of the frontend object
  288.      * @return void
  289.      * @access public
  290.      */
  291.     function setFrontendObject(&$ui)
  292.     {
  293.         $this->ui = &$ui;
  294.     }
  295.  
  296.     // }}}
  297.  
  298.     // {{{ _unIndent()
  299.  
  300.     /**
  301.      * Unindent given string (?)
  302.      *
  303.      * @param string $str The string that has to be unindented.
  304.      * @return string
  305.      * @access private
  306.      */
  307.     function _unIndent($str)
  308.     {
  309.         // remove leading newlines
  310.         $str = preg_replace('/^[\r\n]+/', '', $str);
  311.         // find whitespace at the beginning of the first line
  312.         $indent_len = strspn($str, " \t");
  313.         $indent = substr($str, 0, $indent_len);
  314.         $data = '';
  315.         // remove the same amount of whitespace from following lines
  316.         foreach (explode("\n", $str) as $line) {
  317.             if (substr($line, 0, $indent_len) == $indent) {
  318.                 $data .= substr($line, $indent_len) . "\n";
  319.             }
  320.         }
  321.         return $data;
  322.     }
  323.  
  324.     // }}}
  325.     // {{{ _element_start()
  326.  
  327.     /**
  328.      * XML parser callback for starting elements.  Used while package
  329.      * format version is not yet known.
  330.      *
  331.      * @param resource  $xp       XML parser resource
  332.      * @param string    $name     name of starting element
  333.      * @param array     $attribs  element attributes, name => value
  334.      *
  335.      * @return void
  336.      *
  337.      * @access private
  338.      */
  339.     function _element_start($xp, $name, $attribs)
  340.     {
  341.         array_push($this->element_stack, $name);
  342.         $this->current_element = $name;
  343.         $spos = sizeof($this->element_stack) - 2;
  344.         $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
  345.         $this->current_attributes = $attribs;
  346.         switch ($name) {
  347.             case 'package': {
  348.                 $this->_validPackageFile = true;
  349.                 if (isset($attribs['version'])) {
  350.                     $vs = preg_replace('/[^0-9a-z]/', '_', $attribs['version']);
  351.                 } else {
  352.                     $vs = '1_0';
  353.                 }
  354.                 $elem_start = '_element_start_'. $vs;
  355.                 $elem_end = '_element_end_'. $vs;
  356.                 $cdata = '_pkginfo_cdata_'. $vs;
  357.                 if (!method_exists($this, $elem_start) ||
  358.                       !method_exists($this, $elem_end) ||
  359.                       !method_exists($this, $cdata)) {
  360.                     $this->raiseError("No handlers for package.xml version $attribs[version]");
  361.                     return;
  362.                 }
  363.                 xml_set_element_handler($xp, $elem_start, $elem_end);
  364.                 xml_set_character_data_handler($xp, $cdata);
  365.                 break;
  366.             }
  367.         }
  368.     }
  369.  
  370.     // }}}
  371.     // {{{ _element_end()
  372.  
  373.     /**
  374.      * XML parser callback for ending elements.  Used while package
  375.      * format version is not yet known.
  376.      *
  377.      * @param resource  $xp    XML parser resource
  378.      * @param string    $name  name of ending element
  379.      *
  380.      * @return void
  381.      *
  382.      * @access private
  383.      */
  384.     function _element_end($xp, $name)
  385.     {
  386.     }
  387.  
  388.     // }}}
  389.  
  390.     // Support for package DTD v1.0:
  391.     // {{{ _element_start_1_0()
  392.  
  393.     /**
  394.      * XML parser callback for ending elements.  Used for version 1.0
  395.      * packages.
  396.      *
  397.      * @param resource  $xp    XML parser resource
  398.      * @param string    $name  name of ending element
  399.      *
  400.      * @return void
  401.      *
  402.      * @access private
  403.      */
  404.     function _element_start_1_0($xp, $name, $attribs)
  405.     {
  406.         array_push($this->element_stack, $name);
  407.         $this->current_element = $name;
  408.         $spos = sizeof($this->element_stack) - 2;
  409.         $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
  410.         $this->current_attributes = $attribs;
  411.         $this->cdata = '';
  412.         switch ($name) {
  413.             case 'dir':
  414.                 if ($this->in_changelog) {
  415.                     break;
  416.                 }
  417.                 if ($attribs['name'] != '/') {
  418.                     $this->dir_names[] = $attribs['name'];
  419.                 }
  420.                 if (isset($attribs['baseinstalldir'])) {
  421.                     $this->dir_install = $attribs['baseinstalldir'];
  422.                 }
  423.                 if (isset($attribs['role'])) {
  424.                     $this->dir_role = $attribs['role'];
  425.                 }
  426.                 break;
  427.             case 'file':
  428.                 if ($this->in_changelog) {
  429.                     break;
  430.                 }
  431.                 if (isset($attribs['name'])) {
  432.                     $path = '';
  433.                     if (count($this->dir_names)) {
  434.                         foreach ($this->dir_names as $dir) {
  435.                             $path .= $dir . DIRECTORY_SEPARATOR;
  436.                         }
  437.                     }
  438.                     $path .= $attribs['name'];
  439.                     unset($attribs['name']);
  440.                     $this->current_path = $path;
  441.                     $this->filelist[$path] = $attribs;
  442.                     // Set the baseinstalldir only if the file don't have this attrib
  443.                     if (!isset($this->filelist[$path]['baseinstalldir']) &&
  444.                         isset($this->dir_install))
  445.                     {
  446.                         $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
  447.                     }
  448.                     // Set the Role
  449.                     if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
  450.                         $this->filelist[$path]['role'] = $this->dir_role;
  451.                     }
  452.                 }
  453.                 break;
  454.             case 'replace':
  455.                 if (!$this->in_changelog) {
  456.                     $this->filelist[$this->current_path]['replacements'][] = $attribs;
  457.                 }
  458.                 break;
  459.             case 'maintainers':
  460.                 $this->pkginfo['maintainers'] = array();
  461.                 $this->m_i = 0; // maintainers array index
  462.                 break;
  463.             case 'maintainer':
  464.                 // compatibility check
  465.                 if (!isset($this->pkginfo['maintainers'])) {
  466.                     $this->pkginfo['maintainers'] = array();
  467.                     $this->m_i = 0;
  468.                 }
  469.                 $this->pkginfo['maintainers'][$this->m_i] = array();
  470.                 $this->current_maintainer =& $this->pkginfo['maintainers'][$this->m_i];
  471.                 break;
  472.             case 'changelog':
  473.                 $this->pkginfo['changelog'] = array();
  474.                 $this->c_i = 0; // changelog array index
  475.                 $this->in_changelog = true;
  476.                 break;
  477.             case 'release':
  478.                 if ($this->in_changelog) {
  479.                     $this->pkginfo['changelog'][$this->c_i] = array();
  480.                     $this->current_release = &$this->pkginfo['changelog'][$this->c_i];
  481.                 } else {
  482.                     $this->current_release = &$this->pkginfo;
  483.                 }
  484.                 break;
  485.             case 'deps':
  486.                 if (!$this->in_changelog) {
  487.                     $this->pkginfo['release_deps'] = array();
  488.                 }
  489.                 break;
  490.             case 'dep':
  491.                 // dependencies array index
  492.                 if (!$this->in_changelog) {
  493.                     $this->d_i++;
  494.                     $this->pkginfo['release_deps'][$this->d_i] = $attribs;
  495.                 }
  496.                 break;
  497.             case 'configureoptions':
  498.                 if (!$this->in_changelog) {
  499.                     $this->pkginfo['configure_options'] = array();
  500.                 }
  501.                 break;
  502.             case 'configureoption':
  503.                 if (!$this->in_changelog) {
  504.                     $this->pkginfo['configure_options'][] = $attribs;
  505.                 }
  506.                 break;
  507.             case 'provides':
  508.                 if (empty($attribs['type']) || empty($attribs['name'])) {
  509.                     break;
  510.                 }
  511.                 $attribs['explicit'] = true;
  512.                 $this->pkginfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
  513.                 break;
  514.         }
  515.     }
  516.  
  517.     // }}}
  518.     // {{{ _element_end_1_0()
  519.  
  520.     /**
  521.      * XML parser callback for ending elements.  Used for version 1.0
  522.      * packages.
  523.      *
  524.      * @param resource  $xp    XML parser resource
  525.      * @param string    $name  name of ending element
  526.      *
  527.      * @return void
  528.      *
  529.      * @access private
  530.      */
  531.     function _element_end_1_0($xp, $name)
  532.     {
  533.         $data = trim($this->cdata);
  534.         switch ($name) {
  535.             case 'name':
  536.                 switch ($this->prev_element) {
  537.                     case 'package':
  538.                         // XXX should we check the package name here?
  539.                         $this->pkginfo['package'] = ereg_replace('[^a-zA-Z0-9._]', '_', $data);
  540.                         break;
  541.                     case 'maintainer':
  542.                         $this->current_maintainer['name'] = $data;
  543.                         break;
  544.                 }
  545.                 break;
  546.             case 'summary':
  547.                 $this->pkginfo['summary'] = $data;
  548.                 break;
  549.             case 'description':
  550.                 $data = $this->_unIndent($this->cdata);
  551.                 $this->pkginfo['description'] = $data;
  552.                 break;
  553.             case 'user':
  554.                 $this->current_maintainer['handle'] = $data;
  555.                 break;
  556.             case 'email':
  557.                 $this->current_maintainer['email'] = $data;
  558.                 break;
  559.             case 'role':
  560.                 $this->current_maintainer['role'] = $data;
  561.                 break;
  562.             case 'version':
  563.                 $data = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $data);
  564.                 if ($this->in_changelog) {
  565.                     $this->current_release['version'] = $data;
  566.                 } else {
  567.                     $this->pkginfo['version'] = $data;
  568.                 }
  569.                 break;
  570.             case 'date':
  571.                 if ($this->in_changelog) {
  572.                     $this->current_release['release_date'] = $data;
  573.                 } else {
  574.                     $this->pkginfo['release_date'] = $data;
  575.                 }
  576.                 break;
  577.             case 'notes':
  578.                 // try to "de-indent" release notes in case someone
  579.                 // has been over-indenting their xml ;-)
  580.                 $data = $this->_unIndent($this->cdata);
  581.                 if ($this->in_changelog) {
  582.                     $this->current_release['release_notes'] = $data;
  583.                 } else {
  584.                     $this->pkginfo['release_notes'] = $data;
  585.                 }
  586.                 break;
  587.             case 'warnings':
  588.                 if ($this->in_changelog) {
  589.                     $this->current_release['release_warnings'] = $data;
  590.                 } else {
  591.                     $this->pkginfo['release_warnings'] = $data;
  592.                 }
  593.                 break;
  594.             case 'state':
  595.                 if ($this->in_changelog) {
  596.                     $this->current_release['release_state'] = $data;
  597.                 } else {
  598.                     $this->pkginfo['release_state'] = $data;
  599.                 }
  600.                 break;
  601.             case 'license':
  602.                 if ($this->in_changelog) {
  603.                     $this->current_release['release_license'] = $data;
  604.                 } else {
  605.                     $this->pkginfo['release_license'] = $data;
  606.                 }
  607.                 break;
  608.             case 'dep':
  609.                 if ($data && !$this->in_changelog) {
  610.                     $this->pkginfo['release_deps'][$this->d_i]['name'] = $data;
  611.                 }
  612.                 break;
  613.             case 'dir':
  614.                 if ($this->in_changelog) {
  615.                     break;
  616.                 }
  617.                 array_pop($this->dir_names);
  618.                 break;
  619.             case 'file':
  620.                 if ($this->in_changelog) {
  621.                     break;
  622.                 }
  623.                 if ($data) {
  624.                     $path = '';
  625.                     if (count($this->dir_names)) {
  626.                         foreach ($this->dir_names as $dir) {
  627.                             $path .= $dir . DIRECTORY_SEPARATOR;
  628.                         }
  629.                     }
  630.                     $path .= $data;
  631.                     $this->filelist[$path] = $this->current_attributes;
  632.                     // Set the baseinstalldir only if the file don't have this attrib
  633.                     if (!isset($this->filelist[$path]['baseinstalldir']) &&
  634.                         isset($this->dir_install))
  635.                     {
  636.                         $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
  637.                     }
  638.                     // Set the Role
  639.                     if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
  640.                         $this->filelist[$path]['role'] = $this->dir_role;
  641.                     }
  642.                 }
  643.                 break;
  644.             case 'maintainer':
  645.                 if (empty($this->pkginfo['maintainers'][$this->m_i]['role'])) {
  646.                     $this->pkginfo['maintainers'][$this->m_i]['role'] = 'lead';
  647.                 }
  648.                 $this->m_i++;
  649.                 break;
  650.             case 'release':
  651.                 if ($this->in_changelog) {
  652.                     $this->c_i++;
  653.                 }
  654.                 break;
  655.             case 'changelog':
  656.                 $this->in_changelog = false;
  657.                 break;
  658.         }
  659.         array_pop($this->element_stack);
  660.         $spos = sizeof($this->element_stack) - 1;
  661.         $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
  662.         $this->cdata = '';
  663.     }
  664.  
  665.     // }}}
  666.     // {{{ _pkginfo_cdata_1_0()
  667.  
  668.     /**
  669.      * XML parser callback for character data.  Used for version 1.0
  670.      * packages.
  671.      *
  672.      * @param resource  $xp    XML parser resource
  673.      * @param string    $name  character data
  674.      *
  675.      * @return void
  676.      *
  677.      * @access private
  678.      */
  679.     function _pkginfo_cdata_1_0($xp, $data)
  680.     {
  681.         if (isset($this->cdata)) {
  682.             $this->cdata .= $data;
  683.         }
  684.     }
  685.  
  686.     // }}}
  687.  
  688.     // {{{ infoFromTgzFile()
  689.  
  690.     /**
  691.      * Returns information about a package file.  Expects the name of
  692.      * a gzipped tar file as input.
  693.      *
  694.      * @param string  $file  name of .tgz file
  695.      *
  696.      * @return array  array with package information
  697.      *
  698.      * @access public
  699.      *
  700.      */
  701.     function infoFromTgzFile($file)
  702.     {
  703.         if (!@is_file($file)) {
  704.             return $this->raiseError("could not open file \"$file\"");
  705.         }
  706.         $tar = new Archive_Tar($file);
  707.         if ($this->debug <= 1) {
  708.             $tar->pushErrorHandling(PEAR_ERROR_RETURN);
  709.         }
  710.         $content = $tar->listContent();
  711.         if ($this->debug <= 1) {
  712.             $tar->popErrorHandling();
  713.         }
  714.         if (!is_array($content)) {
  715.             $file = realpath($file);
  716.             return $this->raiseError("Could not get contents of package \"$file\"".
  717.                                      '. Invalid tgz file.');
  718.         }
  719.         $xml = null;
  720.         foreach ($content as $file) {
  721.             $name = $file['filename'];
  722.             if ($name == 'package.xml') {
  723.                 $xml = $name;
  724.                 break;
  725.             } elseif (ereg('package.xml$', $name, $match)) {
  726.                 $xml = $match[0];
  727.                 break;
  728.             }
  729.         }
  730.         $tmpdir = System::mkTemp(array('-d', 'pear'));
  731.         $this->addTempFile($tmpdir);
  732.         if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
  733.             return $this->raiseError('could not extract the package.xml file');
  734.         }
  735.         return $this->infoFromDescriptionFile("$tmpdir/$xml");
  736.     }
  737.  
  738.     // }}}
  739.     // {{{ infoFromDescriptionFile()
  740.  
  741.     /**
  742.      * Returns information about a package file.  Expects the name of
  743.      * a package xml file as input.
  744.      *
  745.      * @param string  $descfile  name of package xml file
  746.      *
  747.      * @return array  array with package information
  748.      *
  749.      * @access public
  750.      *
  751.      */
  752.     function infoFromDescriptionFile($descfile)
  753.     {
  754.         if (!@is_file($descfile) || !is_readable($descfile) ||
  755.              (!$fp = @fopen($descfile, 'r'))) {
  756.             return $this->raiseError("Unable to open $descfile");
  757.         }
  758.  
  759.         // read the whole thing so we only get one cdata callback
  760.         // for each block of cdata
  761.         $data = fread($fp, filesize($descfile));
  762.         return $this->infoFromString($data);
  763.     }
  764.  
  765.     // }}}
  766.     // {{{ infoFromString()
  767.  
  768.     /**
  769.      * Returns information about a package file.  Expects the contents
  770.      * of a package xml file as input.
  771.      *
  772.      * @param string  $data  name of package xml file
  773.      *
  774.      * @return array   array with package information
  775.      *
  776.      * @access public
  777.      *
  778.      */
  779.     function infoFromString($data)
  780.     {
  781.         require_once('PEAR/Dependency.php');
  782.         if (PEAR_Dependency::checkExtension($error, 'xml')) {
  783.             return $this->raiseError($error);
  784.         }
  785.         $xp = @xml_parser_create();
  786.         if (!$xp) {
  787.             return $this->raiseError('Unable to create XML parser');
  788.         }
  789.         xml_set_object($xp, $this);
  790.         xml_set_element_handler($xp, '_element_start', '_element_end');
  791.         xml_set_character_data_handler($xp, '_pkginfo_cdata');
  792.         xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
  793.  
  794.         $this->element_stack = array();
  795.         $this->pkginfo = array('provides' => array());
  796.         $this->current_element = false;
  797.         unset($this->dir_install);
  798.         $this->pkginfo['filelist'] = array();
  799.         $this->filelist =& $this->pkginfo['filelist'];
  800.         $this->dir_names = array();
  801.         $this->in_changelog = false;
  802.         $this->d_i = 0;
  803.         $this->cdata = '';
  804.         $this->_validPackageFile = false;
  805.  
  806.         if (!xml_parse($xp, $data, 1)) {
  807.             $code = xml_get_error_code($xp);
  808.             $msg = sprintf("XML error: %s at line %d",
  809.                            xml_error_string($code),
  810.                            xml_get_current_line_number($xp));
  811.             xml_parser_free($xp);
  812.             return $this->raiseError($msg, $code);
  813.         }
  814.  
  815.         xml_parser_free($xp);
  816.  
  817.         if (!$this->_validPackageFile) {
  818.             return $this->raiseError('Invalid Package File, no <package> tag');
  819.         }
  820.         foreach ($this->pkginfo as $k => $v) {
  821.             if (!is_array($v)) {
  822.                 $this->pkginfo[$k] = trim($v);
  823.             }
  824.         }
  825.         return $this->pkginfo;
  826.     }
  827.     // }}}
  828.     // {{{ infoFromAny()
  829.  
  830.     /**
  831.      * Returns package information from different sources
  832.      *
  833.      * This method is able to extract information about a package
  834.      * from a .tgz archive or from a XML package definition file.
  835.      *
  836.      * @access public
  837.      * @param  string Filename of the source ('package.xml', '<package>.tgz')
  838.      * @return string
  839.      */
  840.     function infoFromAny($info)
  841.     {
  842.         if (is_string($info) && file_exists($info)) {
  843.             $tmp = substr($info, -4);
  844.             if ($tmp == '.xml') {
  845.                 $info = $this->infoFromDescriptionFile($info);
  846.             } elseif ($tmp == '.tar' || $tmp == '.tgz') {
  847.                 $info = $this->infoFromTgzFile($info);
  848.             } else {
  849.                 $fp = fopen($info, "r");
  850.                 $test = fread($fp, 5);
  851.                 fclose($fp);
  852.                 if ($test == "<?xml") {
  853.                     $info = $this->infoFromDescriptionFile($info);
  854.                 } else {
  855.                     $info = $this->infoFromTgzFile($info);
  856.                 }
  857.             }
  858.             if (PEAR::isError($info)) {
  859.                 return $this->raiseError($info);
  860.             }
  861.         }
  862.         return $info;
  863.     }
  864.  
  865.     // }}}
  866.     // {{{ xmlFromInfo()
  867.  
  868.     /**
  869.      * Return an XML document based on the package info (as returned
  870.      * by the PEAR_Common::infoFrom* methods).
  871.      *
  872.      * @param array  $pkginfo  package info
  873.      *
  874.      * @return string XML data
  875.      *
  876.      * @access public
  877.      */
  878.     function xmlFromInfo($pkginfo)
  879.     {
  880.         static $maint_map = array(
  881.             "handle" => "user",
  882.             "name" => "name",
  883.             "email" => "email",
  884.             "role" => "role",
  885.             );
  886.         $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
  887.         $ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n";
  888.         $ret .= "<package version=\"1.0\">
  889.   <name>$pkginfo[package]</name>
  890.   <summary>".htmlspecialchars($pkginfo['summary'])."</summary>
  891.   <description>".htmlspecialchars($pkginfo['description'])."</description>
  892.   <maintainers>
  893. ";
  894.         foreach ($pkginfo['maintainers'] as $maint) {
  895.             $ret .= "    <maintainer>\n";
  896.             foreach ($maint_map as $idx => $elm) {
  897.                 $ret .= "      <$elm>";
  898.                 $ret .= htmlspecialchars($maint[$idx]);
  899.                 $ret .= "</$elm>\n";
  900.             }
  901.             $ret .= "    </maintainer>\n";
  902.         }
  903.         $ret .= "  </maintainers>\n";
  904.         $ret .= $this->_makeReleaseXml($pkginfo);
  905.         if (@sizeof($pkginfo['changelog']) > 0) {
  906.             $ret .= "  <changelog>\n";
  907.             foreach ($pkginfo['changelog'] as $oldrelease) {
  908.                 $ret .= $this->_makeReleaseXml($oldrelease, true);
  909.             }
  910.             $ret .= "  </changelog>\n";
  911.         }
  912.         $ret .= "</package>\n";
  913.         return $ret;
  914.     }
  915.  
  916.     // }}}
  917.     // {{{ _makeReleaseXml()
  918.  
  919.     /**
  920.      * Generate part of an XML description with release information.
  921.      *
  922.      * @param array  $pkginfo    array with release information
  923.      * @param bool   $changelog  whether the result will be in a changelog element
  924.      *
  925.      * @return string XML data
  926.      *
  927.      * @access private
  928.      */
  929.     function _makeReleaseXml($pkginfo, $changelog = false)
  930.     {
  931.         // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!!
  932.         $indent = $changelog ? "  " : "";
  933.         $ret = "$indent  <release>\n";
  934.         if (!empty($pkginfo['version'])) {
  935.             $ret .= "$indent    <version>$pkginfo[version]</version>\n";
  936.         }
  937.         if (!empty($pkginfo['release_date'])) {
  938.             $ret .= "$indent    <date>$pkginfo[release_date]</date>\n";
  939.         }
  940.         if (!empty($pkginfo['release_license'])) {
  941.             $ret .= "$indent    <license>$pkginfo[release_license]</license>\n";
  942.         }
  943.         if (!empty($pkginfo['release_state'])) {
  944.             $ret .= "$indent    <state>$pkginfo[release_state]</state>\n";
  945.         }
  946.         if (!empty($pkginfo['release_notes'])) {
  947.             $ret .= "$indent    <notes>".htmlspecialchars($pkginfo['release_notes'])."</notes>\n";
  948.         }
  949.         if (!empty($pkginfo['release_warnings'])) {
  950.             $ret .= "$indent    <warnings>".htmlspecialchars($pkginfo['release_warnings'])."</warnings>\n";
  951.         }
  952.         if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
  953.             $ret .= "$indent    <deps>\n";
  954.             foreach ($pkginfo['release_deps'] as $dep) {
  955.                 $ret .= "$indent      <dep type=\"$dep[type]\" rel=\"$dep[rel]\"";
  956.                 if (isset($dep['version'])) {
  957.                     $ret .= " version=\"$dep[version]\"";
  958.                 }
  959.                 if (isset($dep['optional'])) {
  960.                     $ret .= " optional=\"$dep[optional]\"";
  961.                 }
  962.                 if (isset($dep['name'])) {
  963.                     $ret .= ">$dep[name]</dep>\n";
  964.                 } else {
  965.                     $ret .= "/>\n";
  966.                 }
  967.             }
  968.             $ret .= "$indent    </deps>\n";
  969.         }
  970.         if (isset($pkginfo['configure_options'])) {
  971.             $ret .= "$indent    <configureoptions>\n";
  972.             foreach ($pkginfo['configure_options'] as $c) {
  973.                 $ret .= "$indent      <configureoption name=\"".
  974.                     htmlspecialchars($c['name']) . "\"";
  975.                 if (isset($c['default'])) {
  976.                     $ret .= " default=\"" . htmlspecialchars($c['default']) . "\"";
  977.                 }
  978.                 $ret .= " prompt=\"" . htmlspecialchars($c['prompt']) . "\"";
  979.                 $ret .= "/>\n";
  980.             }
  981.             $ret .= "$indent    </configureoptions>\n";
  982.         }
  983.         if (isset($pkginfo['provides'])) {
  984.             foreach ($pkginfo['provides'] as $key => $what) {
  985.                 $ret .= "$indent    <provides type=\"$what[type]\" ";
  986.                 $ret .= "name=\"$what[name]\" ";
  987.                 if (isset($what['extends'])) {
  988.                     $ret .= "extends=\"$what[extends]\" ";
  989.                 }
  990.                 $ret .= "/>\n";
  991.             }
  992.         }
  993.         if (isset($pkginfo['filelist'])) {
  994.             $ret .= "$indent    <filelist>\n";
  995.             foreach ($pkginfo['filelist'] as $file => $fa) {
  996.                 @$ret .= "$indent      <file role=\"$fa[role]\"";
  997.                 if (isset($fa['baseinstalldir'])) {
  998.                     $ret .= ' baseinstalldir="' .
  999.                         htmlspecialchars($fa['baseinstalldir']) . '"';
  1000.                 }
  1001.                 if (isset($fa['md5sum'])) {
  1002.                     $ret .= " md5sum=\"$fa[md5sum]\"";
  1003.                 }
  1004.                 if (isset($fa['platform'])) {
  1005.                     $ret .= " platform=\"$fa[platform]\"";
  1006.                 }
  1007.                 if (!empty($fa['install-as'])) {
  1008.                     $ret .= ' install-as="' .
  1009.                         htmlspecialchars($fa['install-as']) . '"';
  1010.                 }
  1011.                 $ret .= ' name="' . htmlspecialchars($file) . '"';
  1012.                 if (empty($fa['replacements'])) {
  1013.                     $ret .= "/>\n";
  1014.                 } else {
  1015.                     $ret .= ">\n";
  1016.                     foreach ($fa['replacements'] as $r) {
  1017.                         $ret .= "$indent        <replace";
  1018.                         foreach ($r as $k => $v) {
  1019.                             $ret .= " $k=\"" . htmlspecialchars($v) .'"';
  1020.                         }
  1021.                         $ret .= "/>\n";
  1022.                     }
  1023.                     @$ret .= "$indent      </file>\n";
  1024.                 }
  1025.             }
  1026.             $ret .= "$indent    </filelist>\n";
  1027.         }
  1028.         $ret .= "$indent  </release>\n";
  1029.         return $ret;
  1030.     }
  1031.  
  1032.     // }}}
  1033.     // {{{ validatePackageInfo()
  1034.  
  1035.     /**
  1036.      * Validate XML package definition file.
  1037.      *
  1038.      * @param  string $info Filename of the package archive or of the
  1039.      *                package definition file
  1040.      * @param  array $errors Array that will contain the errors
  1041.      * @param  array $warnings Array that will contain the warnings
  1042.      * @param  string $dir_prefix (optional) directory where source files
  1043.      *                may be found, or empty if they are not available
  1044.      * @access public
  1045.      * @return boolean
  1046.      */
  1047.     function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '')
  1048.     {
  1049.         if (PEAR::isError($info = $this->infoFromAny($info))) {
  1050.             return $this->raiseError($info);
  1051.         }
  1052.         if (!is_array($info)) {
  1053.             return false;
  1054.         }
  1055.  
  1056.         $errors = array();
  1057.         $warnings = array();
  1058.         if (!isset($info['package'])) {
  1059.             $errors[] = 'missing package name';
  1060.         } elseif (!$this->validPackageName($info['package'])) {
  1061.             $errors[] = 'invalid package name';
  1062.         }
  1063.         $this->_packageName = $pn = $info['package'];
  1064.  
  1065.         if (empty($info['summary'])) {
  1066.             $errors[] = 'missing summary';
  1067.         } elseif (strpos(trim($info['summary']), "\n") !== false) {
  1068.             $warnings[] = 'summary should be on a single line';
  1069.         }
  1070.         if (empty($info['description'])) {
  1071.             $errors[] = 'missing description';
  1072.         }
  1073.         if (empty($info['release_license'])) {
  1074.             $errors[] = 'missing license';
  1075.         }
  1076.         if (!isset($info['version'])) {
  1077.             $errors[] = 'missing version';
  1078.         } elseif (!$this->validPackageVersion($info['version'])) {
  1079.             $errors[] = 'invalid package version';
  1080.         }
  1081.         if (empty($info['release_state'])) {
  1082.             $errors[] = 'missing release state';
  1083.         } elseif (!in_array($info['release_state'], PEAR_Common::getReleaseStates())) {
  1084.             $errors[] = "invalid release state `$info[release_state]', should be one of: "
  1085.                 . implode(' ', PEAR_Common::getReleaseStates());
  1086.         }
  1087.         if (empty($info['release_date'])) {
  1088.             $errors[] = 'missing release date';
  1089.         } elseif (!preg_match('/^\d{4}-\d\d-\d\d$/', $info['release_date'])) {
  1090.             $errors[] = "invalid release date `$info[release_date]', format is YYYY-MM-DD";
  1091.         }
  1092.         if (empty($info['release_notes'])) {
  1093.             $errors[] = "missing release notes";
  1094.         }
  1095.         if (empty($info['maintainers'])) {
  1096.             $errors[] = 'no maintainer(s)';
  1097.         } else {
  1098.             $i = 1;
  1099.             foreach ($info['maintainers'] as $m) {
  1100.                 if (empty($m['handle'])) {
  1101.                     $errors[] = "maintainer $i: missing handle";
  1102.                 }
  1103.                 if (empty($m['role'])) {
  1104.                     $errors[] = "maintainer $i: missing role";
  1105.                 } elseif (!in_array($m['role'], PEAR_Common::getUserRoles())) {
  1106.                     $errors[] = "maintainer $i: invalid role `$m[role]', should be one of: "
  1107.                         . implode(' ', PEAR_Common::getUserRoles());
  1108.                 }
  1109.                 if (empty($m['name'])) {
  1110.                     $errors[] = "maintainer $i: missing name";
  1111.                 }
  1112.                 if (empty($m['email'])) {
  1113.                     $errors[] = "maintainer $i: missing email";
  1114.                 }
  1115.                 $i++;
  1116.             }
  1117.         }
  1118.         if (!empty($info['deps'])) {
  1119.             $i = 1;
  1120.             foreach ($info['deps'] as $d) {
  1121.                 if (empty($d['type'])) {
  1122.                     $errors[] = "dependency $i: missing type";
  1123.                 } elseif (!in_array($d['type'], PEAR_Common::getDependencyTypes())) {
  1124.                     $errors[] = "dependency $i: invalid type, should be one of: " .
  1125.                         implode(' ', PEAR_Common::getDependencyTypes());
  1126.                 }
  1127.                 if (empty($d['rel'])) {
  1128.                     $errors[] = "dependency $i: missing relation";
  1129.                 } elseif (!in_array($d['rel'], PEAR_Common::getDependencyRelations())) {
  1130.                     $errors[] = "dependency $i: invalid relation, should be one of: "
  1131.                         . implode(' ', PEAR_Common::getDependencyRelations());
  1132.                 }
  1133.                 if (!empty($d['optional'])) {
  1134.                     if (!in_array($d['optional'], array('yes', 'no'))) {
  1135.                         $errors[] = "dependency $i: invalid relation optional attribute, should be one of: yes no";
  1136.                     }
  1137.                 }
  1138.                 if ($d['rel'] != 'has' && empty($d['version'])) {
  1139.                     $warnings[] = "dependency $i: missing version";
  1140.                 } elseif ($d['rel'] == 'has' && !empty($d['version'])) {
  1141.                     $warnings[] = "dependency $i: version ignored for `has' dependencies";
  1142.                 }
  1143.                 if ($d['type'] == 'php' && !empty($d['name'])) {
  1144.                     $warnings[] = "dependency $i: name ignored for php type dependencies";
  1145.                 } elseif ($d['type'] != 'php' && empty($d['name'])) {
  1146.                     $errors[] = "dependency $i: missing name";
  1147.                 }
  1148.                 $i++;
  1149.             }
  1150.         }
  1151.         if (!empty($info['configure_options'])) {
  1152.             $i = 1;
  1153.             foreach ($info['configure_options'] as $c) {
  1154.                 if (empty($c['name'])) {
  1155.                     $errors[] = "configure option $i: missing name";
  1156.                 }
  1157.                 if (empty($c['prompt'])) {
  1158.                     $errors[] = "configure option $i: missing prompt";
  1159.                 }
  1160.                 $i++;
  1161.             }
  1162.         }
  1163.         if (empty($info['filelist'])) {
  1164.             $errors[] = 'no files';
  1165.         } else {
  1166.             foreach ($info['filelist'] as $file => $fa) {
  1167.                 if (empty($fa['role'])) {
  1168.                     $errors[] = "file $file: missing role";
  1169.                     continue;
  1170.                 } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) {
  1171.                     $errors[] = "file $file: invalid role, should be one of: "
  1172.                         . implode(' ', PEAR_Common::getFileRoles());
  1173.                 }
  1174.                 if ($fa['role'] == 'php' && $dir_prefix) {
  1175.                     $this->log(1, "Analyzing $file");
  1176.                     $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
  1177.                     if ($srcinfo) {
  1178.                         $this->buildProvidesArray($srcinfo);
  1179.                     }
  1180.                 }
  1181.  
  1182.                 // (ssb) Any checks we can do for baseinstalldir?
  1183.                 // (cox) Perhaps checks that either the target dir and
  1184.                 //       baseInstall doesn't cointain "../../"
  1185.             }
  1186.         }
  1187.         $this->_packageName = $pn = $info['package'];
  1188.         $pnl = strlen($pn);
  1189.         foreach ((array)$this->pkginfo['provides'] as $key => $what) {
  1190.             if (isset($what['explicit'])) {
  1191.                 // skip conformance checks if the provides entry is
  1192.                 // specified in the package.xml file
  1193.                 continue;
  1194.             }
  1195.             extract($what);
  1196.             if ($type == 'class') {
  1197.                 if (!strncasecmp($name, $pn, $pnl)) {
  1198.                     continue;
  1199.                 }
  1200.                 $warnings[] = "in $file: class \"$name\" not prefixed with package name \"$pn\"";
  1201.             } elseif ($type == 'function') {
  1202.                 if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
  1203.                     continue;
  1204.                 }
  1205.                 $warnings[] = "in $file: function \"$name\" not prefixed with package name \"$pn\"";
  1206.             }
  1207.         }
  1208.  
  1209.  
  1210.         return true;
  1211.     }
  1212.  
  1213.     // }}}
  1214.     // {{{ buildProvidesArray()
  1215.  
  1216.     /**
  1217.      * Build a "provides" array from data returned by
  1218.      * analyzeSourceCode().  The format of the built array is like
  1219.      * this:
  1220.      *
  1221.      *  array(
  1222.      *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
  1223.      *    ...
  1224.      *  )
  1225.      *
  1226.      *
  1227.      * @param array $srcinfo array with information about a source file
  1228.      * as returned by the analyzeSourceCode() method.
  1229.      *
  1230.      * @return void
  1231.      *
  1232.      * @access public
  1233.      *
  1234.      */
  1235.     function buildProvidesArray($srcinfo)
  1236.     {
  1237.         $file = basename($srcinfo['source_file']);
  1238.         $pn  = $this->_packageName;
  1239.         $pnl = strlen($pn);
  1240.         foreach ($srcinfo['declared_classes'] as $class) {
  1241.             $key = "class;$class";
  1242.             if (isset($this->pkginfo['provides'][$key])) {
  1243.                 continue;
  1244.             }
  1245.             $this->pkginfo['provides'][$key] =
  1246.                 array('file'=> $file, 'type' => 'class', 'name' => $class);
  1247.             if (isset($srcinfo['inheritance'][$class])) {
  1248.                 $this->pkginfo['provides'][$key]['extends'] =
  1249.                     $srcinfo['inheritance'][$class];
  1250.             }
  1251.         }
  1252.         foreach ($srcinfo['declared_methods'] as $class => $methods) {
  1253.             foreach ($methods as $method) {
  1254.                 $function = "$class::$method";
  1255.                 $key = "function;$function";
  1256.                 if ($method{0} == '_' || !strcasecmp($method, $class) ||
  1257.                     isset($this->pkginfo['provides'][$key])) {
  1258.                     continue;
  1259.                 }
  1260.                 $this->pkginfo['provides'][$key] =
  1261.                     array('file'=> $file, 'type' => 'function', 'name' => $function);
  1262.             }
  1263.         }
  1264.  
  1265.         foreach ($srcinfo['declared_functions'] as $function) {
  1266.             $key = "function;$function";
  1267.             if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) {
  1268.                 continue;
  1269.             }
  1270.             if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
  1271.                 $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
  1272.             }
  1273.             $this->pkginfo['provides'][$key] =
  1274.                 array('file'=> $file, 'type' => 'function', 'name' => $function);
  1275.         }
  1276.     }
  1277.  
  1278.     // }}}
  1279.     // {{{ analyzeSourceCode()
  1280.  
  1281.     /**
  1282.      * Analyze the source code of the given PHP file
  1283.      *
  1284.      * @param  string Filename of the PHP file
  1285.      * @return mixed
  1286.      * @access public
  1287.      */
  1288.     function analyzeSourceCode($file)
  1289.     {
  1290.         if (!function_exists("token_get_all")) {
  1291.             return false;
  1292.         }
  1293.         if (!$fp = @fopen($file, "r")) {
  1294.             return false;
  1295.         }
  1296.         $contents = fread($fp, filesize($file));
  1297.         $tokens = token_get_all($contents);
  1298. /*
  1299.         for ($i = 0; $i < sizeof($tokens); $i++) {
  1300.             @list($token, $data) = $tokens[$i];
  1301.             if (is_string($token)) {
  1302.                 var_dump($token);
  1303.             } else {
  1304.                 print token_name($token) . ' ';
  1305.                 var_dump(rtrim($data));
  1306.             }
  1307.         }
  1308. */
  1309.         $look_for = 0;
  1310.         $paren_level = 0;
  1311.         $bracket_level = 0;
  1312.         $brace_level = 0;
  1313.         $lastphpdoc = '';
  1314.         $current_class = '';
  1315.         $current_class_level = -1;
  1316.         $current_function = '';
  1317.         $current_function_level = -1;
  1318.         $declared_classes = array();
  1319.         $declared_functions = array();
  1320.         $declared_methods = array();
  1321.         $used_classes = array();
  1322.         $used_functions = array();
  1323.         $extends = array();
  1324.         $nodeps = array();
  1325.         $inquote = false;
  1326.         for ($i = 0; $i < sizeof($tokens); $i++) {
  1327.             if (is_array($tokens[$i])) {
  1328.                 list($token, $data) = $tokens[$i];
  1329.             } else {
  1330.                 $token = $tokens[$i];
  1331.                 $data = '';
  1332.             }
  1333.             if ($inquote) {
  1334.                 if ($token != '"') {
  1335.                     continue;
  1336.                 } else {
  1337.                     $inquote = false;
  1338.                 }
  1339.             }
  1340.             switch ($token) {
  1341.                 case '"':
  1342.                     $inquote = true;
  1343.                     break;
  1344.                 case T_CURLY_OPEN:
  1345.                 case T_DOLLAR_OPEN_CURLY_BRACES:
  1346.                 case '{': $brace_level++; continue 2;
  1347.                 case '}':
  1348.                     $brace_level--;
  1349.                     if ($current_class_level == $brace_level) {
  1350.                         $current_class = '';
  1351.                         $current_class_level = -1;
  1352.                     }
  1353.                     if ($current_function_level == $brace_level) {
  1354.                         $current_function = '';
  1355.                         $current_function_level = -1;
  1356.                     }
  1357.                     continue 2;
  1358.                 case '[': $bracket_level++; continue 2;
  1359.                 case ']': $bracket_level--; continue 2;
  1360.                 case '(': $paren_level++;   continue 2;
  1361.                 case ')': $paren_level--;   continue 2;
  1362.                 case T_CLASS:
  1363.                     if (($current_class_level != -1) || ($current_function_level != -1)) {
  1364.                         PEAR::raiseError("Parser error: Invalid PHP file $file",
  1365.                             PEAR_COMMON_ERROR_INVALIDPHP);
  1366.                         return false;
  1367.                     }
  1368.                 case T_FUNCTION:
  1369.                 case T_NEW:
  1370.                 case T_EXTENDS:
  1371.                     $look_for = $token;
  1372.                     continue 2;
  1373.                 case T_STRING:
  1374.                     if ($look_for == T_CLASS) {
  1375.                         $current_class = $data;
  1376.                         $current_class_level = $brace_level;
  1377.                         $declared_classes[] = $current_class;
  1378.                     } elseif ($look_for == T_EXTENDS) {
  1379.                         $extends[$current_class] = $data;
  1380.                     } elseif ($look_for == T_FUNCTION) {
  1381.                         if ($current_class) {
  1382.                             $current_function = "$current_class::$data";
  1383.                             $declared_methods[$current_class][] = $data;
  1384.                         } else {
  1385.                             $current_function = $data;
  1386.                             $declared_functions[] = $current_function;
  1387.                         }
  1388.                         $current_function_level = $brace_level;
  1389.                         $m = array();
  1390.                     } elseif ($look_for == T_NEW) {
  1391.                         $used_classes[$data] = true;
  1392.                     }
  1393.                     $look_for = 0;
  1394.                     continue 2;
  1395.                 case T_VARIABLE:
  1396.                     $look_for = 0;
  1397.                     continue 2;
  1398.                 case T_COMMENT:
  1399.                     if (preg_match('!^/\*\*\s!', $data)) {
  1400.                         $lastphpdoc = $data;
  1401.                         if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
  1402.                             $nodeps = array_merge($nodeps, $m[1]);
  1403.                         }
  1404.                     }
  1405.                     continue 2;
  1406.                 case T_DOUBLE_COLON:
  1407.                     if ($tokens[$i - 1][0] != T_STRING) {
  1408.                         PEAR::raiseError("Parser error: Invalid PHP file $file",
  1409.                             PEAR_COMMON_ERROR_INVALIDPHP);
  1410.                         return false;
  1411.                     }
  1412.                     $class = $tokens[$i - 1][1];
  1413.                     if (strtolower($class) != 'parent') {
  1414.                         $used_classes[$class] = true;
  1415.                     }
  1416.                     continue 2;
  1417.             }
  1418.         }
  1419.         return array(
  1420.             "source_file" => $file,
  1421.             "declared_classes" => $declared_classes,
  1422.             "declared_methods" => $declared_methods,
  1423.             "declared_functions" => $declared_functions,
  1424.             "used_classes" => array_diff(array_keys($used_classes), $nodeps),
  1425.             "inheritance" => $extends,
  1426.             );
  1427.     }
  1428.  
  1429.     // }}}
  1430.     // {{{  betterStates()
  1431.  
  1432.     /**
  1433.      * Return an array containing all of the states that are more stable than
  1434.      * or equal to the passed in state
  1435.      *
  1436.      * @param string Release state
  1437.      * @param boolean Determines whether to include $state in the list
  1438.      * @return false|array False if $state is not a valid release state
  1439.      */
  1440.     function betterStates($state, $include = false)
  1441.     {
  1442.         static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
  1443.         $i = array_search($state, $states);
  1444.         if ($i === false) {
  1445.             return false;
  1446.         }
  1447.         if ($include) {
  1448.             $i--;
  1449.         }
  1450.         return array_slice($states, $i + 1);
  1451.     }
  1452.  
  1453.     // }}}
  1454.     // {{{ detectDependencies()
  1455.  
  1456.     function detectDependencies($any, $status_callback = null)
  1457.     {
  1458.         if (!function_exists("token_get_all")) {
  1459.             return false;
  1460.         }
  1461.         if (PEAR::isError($info = $this->infoFromAny($any))) {
  1462.             return $this->raiseError($info);
  1463.         }
  1464.         if (!is_array($info)) {
  1465.             return false;
  1466.         }
  1467.         $deps = array();
  1468.         $used_c = $decl_c = $decl_f = $decl_m = array();
  1469.         foreach ($info['filelist'] as $file => $fa) {
  1470.             $tmp = $this->analyzeSourceCode($file);
  1471.             $used_c = @array_merge($used_c, $tmp['used_classes']);
  1472.             $decl_c = @array_merge($decl_c, $tmp['declared_classes']);
  1473.             $decl_f = @array_merge($decl_f, $tmp['declared_functions']);
  1474.             $decl_m = @array_merge($decl_m, $tmp['declared_methods']);
  1475.             $inheri = @array_merge($inheri, $tmp['inheritance']);
  1476.         }
  1477.         $used_c = array_unique($used_c);
  1478.         $decl_c = array_unique($decl_c);
  1479.         $undecl_c = array_diff($used_c, $decl_c);
  1480.         return array('used_classes' => $used_c,
  1481.                      'declared_classes' => $decl_c,
  1482.                      'declared_methods' => $decl_m,
  1483.                      'declared_functions' => $decl_f,
  1484.                      'undeclared_classes' => $undecl_c,
  1485.                      'inheritance' => $inheri,
  1486.                      );
  1487.     }
  1488.  
  1489.     // }}}
  1490.     // {{{ getUserRoles()
  1491.  
  1492.     /**
  1493.      * Get the valid roles for a PEAR package maintainer
  1494.      *
  1495.      * @return array
  1496.      * @static
  1497.      */
  1498.     function getUserRoles()
  1499.     {
  1500.         return $GLOBALS['_PEAR_Common_maintainer_roles'];
  1501.     }
  1502.  
  1503.     // }}}
  1504.     // {{{ getReleaseStates()
  1505.  
  1506.     /**
  1507.      * Get the valid package release states of packages
  1508.      *
  1509.      * @return array
  1510.      * @static
  1511.      */
  1512.     function getReleaseStates()
  1513.     {
  1514.         return $GLOBALS['_PEAR_Common_release_states'];
  1515.     }
  1516.  
  1517.     // }}}
  1518.     // {{{ getDependencyTypes()
  1519.  
  1520.     /**
  1521.      * Get the implemented dependency types (php, ext, pkg etc.)
  1522.      *
  1523.      * @return array
  1524.      * @static
  1525.      */
  1526.     function getDependencyTypes()
  1527.     {
  1528.         return $GLOBALS['_PEAR_Common_dependency_types'];
  1529.     }
  1530.  
  1531.     // }}}
  1532.     // {{{ getDependencyRelations()
  1533.  
  1534.     /**
  1535.      * Get the implemented dependency relations (has, lt, ge etc.)
  1536.      *
  1537.      * @return array
  1538.      * @static
  1539.      */
  1540.     function getDependencyRelations()
  1541.     {
  1542.         return $GLOBALS['_PEAR_Common_dependency_relations'];
  1543.     }
  1544.  
  1545.     // }}}
  1546.     // {{{ getFileRoles()
  1547.  
  1548.     /**
  1549.      * Get the implemented file roles
  1550.      *
  1551.      * @return array
  1552.      * @static
  1553.      */
  1554.     function getFileRoles()
  1555.     {
  1556.         return $GLOBALS['_PEAR_Common_file_roles'];
  1557.     }
  1558.  
  1559.     // }}}
  1560.     // {{{ getReplacementTypes()
  1561.  
  1562.     /**
  1563.      * Get the implemented file replacement types in
  1564.      *
  1565.      * @return array
  1566.      * @static
  1567.      */
  1568.     function getReplacementTypes()
  1569.     {
  1570.         return $GLOBALS['_PEAR_Common_replacement_types'];
  1571.     }
  1572.  
  1573.     // }}}
  1574.     // {{{ getProvideTypes()
  1575.  
  1576.     /**
  1577.      * Get the implemented file replacement types in
  1578.      *
  1579.      * @return array
  1580.      * @static
  1581.      */
  1582.     function getProvideTypes()
  1583.     {
  1584.         return $GLOBALS['_PEAR_Common_provide_types'];
  1585.     }
  1586.  
  1587.     // }}}
  1588.     // {{{ getScriptPhases()
  1589.  
  1590.     /**
  1591.      * Get the implemented file replacement types in
  1592.      *
  1593.      * @return array
  1594.      * @static
  1595.      */
  1596.     function getScriptPhases()
  1597.     {
  1598.         return $GLOBALS['_PEAR_Common_script_phases'];
  1599.     }
  1600.  
  1601.     // }}}
  1602.     // {{{ validPackageName()
  1603.  
  1604.     /**
  1605.      * Test whether a string contains a valid package name.
  1606.      *
  1607.      * @param string $name the package name to test
  1608.      *
  1609.      * @return bool
  1610.      *
  1611.      * @access public
  1612.      */
  1613.     function validPackageName($name)
  1614.     {
  1615.         return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name);
  1616.     }
  1617.  
  1618.  
  1619.     // }}}
  1620.     // {{{ validPackageVersion()
  1621.  
  1622.     /**
  1623.      * Test whether a string contains a valid package version.
  1624.      *
  1625.      * @param string $ver the package version to test
  1626.      *
  1627.      * @return bool
  1628.      *
  1629.      * @access public
  1630.      */
  1631.     function validPackageVersion($ver)
  1632.     {
  1633.         return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
  1634.     }
  1635.  
  1636.  
  1637.     // }}}
  1638.  
  1639.     // {{{ downloadHttp()
  1640.  
  1641.     /**
  1642.      * Download a file through HTTP.  Considers suggested file name in
  1643.      * Content-disposition: header and can run a callback function for
  1644.      * different events.  The callback will be called with two
  1645.      * parameters: the callback type, and parameters.  The implemented
  1646.      * callback types are:
  1647.      *
  1648.      *  'setup'       called at the very beginning, parameter is a UI object
  1649.      *                that should be used for all output
  1650.      *  'message'     the parameter is a string with an informational message
  1651.      *  'saveas'      may be used to save with a different file name, the
  1652.      *                parameter is the filename that is about to be used.
  1653.      *                If a 'saveas' callback returns a non-empty string,
  1654.      *                that file name will be used as the filename instead.
  1655.      *                Note that $save_dir will not be affected by this, only
  1656.      *                the basename of the file.
  1657.      *  'start'       download is starting, parameter is number of bytes
  1658.      *                that are expected, or -1 if unknown
  1659.      *  'bytesread'   parameter is the number of bytes read so far
  1660.      *  'done'        download is complete, parameter is the total number
  1661.      *                of bytes read
  1662.      *  'connfailed'  if the TCP connection fails, this callback is called
  1663.      *                with array(host,port,errno,errmsg)
  1664.      *  'writefailed' if writing to disk fails, this callback is called
  1665.      *                with array(destfile,errmsg)
  1666.      *
  1667.      * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1668.      * setting), the proxy will be used.
  1669.      *
  1670.      * @param string  $url       the URL to download
  1671.      * @param object  $ui        PEAR_Frontend_* instance
  1672.      * @param object  $config    PEAR_Config instance
  1673.      * @param string  $save_dir  (optional) directory to save file in
  1674.      * @param mixed   $callback  (optional) function/method to call for status
  1675.      *                           updates
  1676.      *
  1677.      * @return string  Returns the full path of the downloaded file or a PEAR
  1678.      *                 error on failure.  If the error is caused by
  1679.      *                 socket-related errors, the error object will
  1680.      *                 have the fsockopen error code available through
  1681.      *                 getCode().
  1682.      *
  1683.      * @access public
  1684.      */
  1685.     function downloadHttp($url, &$ui, $save_dir = '.', $callback = null)
  1686.     {
  1687.         if ($callback) {
  1688.             call_user_func($callback, 'setup', array(&$ui));
  1689.         }
  1690.         if (preg_match('!^http://([^/:?#]*)(:(\d+))?(/.*)!', $url, $matches)) {
  1691.             list(,$host,,$port,$path) = $matches;
  1692.         }
  1693.         if (isset($this)) {
  1694.             $config = &$this->config;
  1695.         } else {
  1696.             $config = &PEAR_Config::singleton();
  1697.         }
  1698.         $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
  1699.         if ($proxy = parse_url($config->get('http_proxy'))) {
  1700.             $proxy_host = @$proxy['host'];
  1701.             $proxy_port = @$proxy['port'];
  1702.             $proxy_user = @$proxy['user'];
  1703.             $proxy_pass = @$proxy['pass'];
  1704.  
  1705.             if ($proxy_port == '') {
  1706.                 $proxy_port = 8080;
  1707.             }
  1708.             if ($callback) {
  1709.                 call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
  1710.             }
  1711.         }
  1712.         if (empty($port)) {
  1713.             $port = 80;
  1714.         }
  1715.         if ($proxy_host != '') {
  1716.             $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
  1717.             if (!$fp) {
  1718.                 if ($callback) {
  1719.                     call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
  1720.                                                                   $errno, $errstr));
  1721.                 }
  1722.                 return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
  1723.             }
  1724.             $request = "GET $url HTTP/1.0\r\n";
  1725.         } else {
  1726.             $fp = @fsockopen($host, $port, $errno, $errstr);
  1727.             if (!$fp) {
  1728.                 if ($callback) {
  1729.                     call_user_func($callback, 'connfailed', array($host, $port,
  1730.                                                                   $errno, $errstr));
  1731.                 }
  1732.                 return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
  1733.             }
  1734.             $request = "GET $path HTTP/1.0\r\n";
  1735.         }
  1736.         $request .= "Host: $host:$port\r\n".
  1737.             "User-Agent: PHP/".PHP_VERSION."\r\n";
  1738.         if ($proxy_host != '' && $proxy_user != '') {
  1739.             $request .= 'Proxy-Authorization: Basic ' .
  1740.                 base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
  1741.         }
  1742.         $request .= "\r\n";
  1743.         fwrite($fp, $request);
  1744.         $headers = array();
  1745.         while (trim($line = fgets($fp, 1024))) {
  1746.             if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) {
  1747.                 $headers[strtolower($matches[1])] = trim($matches[2]);
  1748.             } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
  1749.                 if ($matches[1] != 200) {
  1750.                     return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)");
  1751.                 }
  1752.             }
  1753.         }
  1754.         if (isset($headers['content-disposition']) &&
  1755.             preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/', $headers['content-disposition'], $matches)) {
  1756.             $save_as = basename($matches[1]);
  1757.         } else {
  1758.             $save_as = basename($url);
  1759.         }
  1760.         if ($callback) {
  1761.             $tmp = call_user_func($callback, 'saveas', $save_as);
  1762.             if ($tmp) {
  1763.                 $save_as = $tmp;
  1764.             }
  1765.         }
  1766.         $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1767.         if (!$wp = @fopen($dest_file, 'wb')) {
  1768.             fclose($fp);
  1769.             if ($callback) {
  1770.                 call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1771.             }
  1772.             return PEAR::raiseError("could not open $dest_file for writing");
  1773.         }
  1774.         if (isset($headers['content-length'])) {
  1775.             $length = $headers['content-length'];
  1776.         } else {
  1777.             $length = -1;
  1778.         }
  1779.         $bytes = 0;
  1780.         if ($callback) {
  1781.             call_user_func($callback, 'start', array(basename($dest_file), $length));
  1782.         }
  1783.         while ($data = @fread($fp, 1024)) {
  1784.             $bytes += strlen($data);
  1785.             if ($callback) {
  1786.                 call_user_func($callback, 'bytesread', $bytes);
  1787.             }
  1788.             if (!@fwrite($wp, $data)) {
  1789.                 fclose($fp);
  1790.                 if ($callback) {
  1791.                     call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1792.                 }
  1793.                 return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
  1794.             }
  1795.         }
  1796.         fclose($fp);
  1797.         fclose($wp);
  1798.         if ($callback) {
  1799.             call_user_func($callback, 'done', $bytes);
  1800.         }
  1801.         return $dest_file;
  1802.     }
  1803.  
  1804.     // }}}
  1805.     // {{{ sortPkgDeps()
  1806.  
  1807.     /**
  1808.      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1809.      *
  1810.      * It also removes duplicate dependencies
  1811.      * @param array
  1812.      * @param boolean Sort packages in reverse order if true
  1813.      * @return array array of array(packagefilename, package.xml contents)
  1814.      */
  1815.     function sortPkgDeps(&$packages, $uninstall = false)
  1816.     {
  1817.         $ret = array();
  1818.         if ($uninstall) {
  1819.             foreach($packages as $packageinfo) {
  1820.                 $ret[] = array('info' => $packageinfo);
  1821.             }
  1822.         } else {
  1823.             foreach($packages as $packagefile) {
  1824.                 if (!is_array($packagefile)) {
  1825.                     $ret[] = array('file' => $packagefile,
  1826.                                    'info' => $a = $this->infoFromAny($packagefile),
  1827.                                    'pkg' => $a['package']);
  1828.                 } else {
  1829.                     $ret[] = $packagefile;
  1830.                 }
  1831.             }
  1832.         }
  1833.         $checkdupes = array();
  1834.         $newret = array();
  1835.         foreach($ret as $i => $p) {
  1836.             if (!isset($checkdupes[$p['info']['package']])) {
  1837.                 $checkdupes[$p['info']['package']][] = $i;
  1838.                 $newret[] = $p;
  1839.             }
  1840.         }
  1841.         $this->_packageSortTree = $this->_getPkgDepTree($newret);
  1842.  
  1843.         $func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps';
  1844.         usort($newret, array(&$this, $func));
  1845.         $this->_packageSortTree = null;
  1846.         $packages = $newret;
  1847.     }
  1848.  
  1849.     // }}}
  1850.     // {{{ _sortPkgDeps()
  1851.  
  1852.     /**
  1853.      * Compare two package's package.xml, and sort
  1854.      * so that dependencies are installed first
  1855.      *
  1856.      * This is a crude compare, real dependency checking is done on install.
  1857.      * The only purpose this serves is to make the command-line
  1858.      * order-independent (you can list a dependent package first, and
  1859.      * installation occurs in the order required)
  1860.      * @access private
  1861.      */
  1862.     function _sortPkgDeps($p1, $p2)
  1863.     {
  1864.         $p1name = $p1['info']['package'];
  1865.         $p2name = $p2['info']['package'];
  1866.         $p1deps = $this->_getPkgDeps($p1);
  1867.         $p2deps = $this->_getPkgDeps($p2);
  1868.         if (!count($p1deps) && !count($p2deps)) {
  1869.             return 0; // order makes no difference
  1870.         }
  1871.         if (!count($p1deps)) {
  1872.             return -1; // package 2 has dependencies, package 1 doesn't
  1873.         }
  1874.         if (!count($p2deps)) {
  1875.             return 1; // package 1 has dependencies, package 2 doesn't
  1876.         }
  1877.         // both have dependencies
  1878.         if (in_array($p1name, $p2deps)) {
  1879.             return -1; // put package 1 first: package 2 depends on package 1
  1880.         }
  1881.         if (in_array($p2name, $p1deps)) {
  1882.             return 1; // put package 2 first: package 1 depends on package 2
  1883.         }
  1884.         if ($this->_removedDependency($p1name, $p2name)) {
  1885.             return -1; // put package 1 first: package 2 depends on packages that depend on package 1
  1886.         }
  1887.         if ($this->_removedDependency($p2name, $p1name)) {
  1888.             return 1; // put package 2 first: package 1 depends on packages that depend on package 2
  1889.         }
  1890.         // doesn't really matter if neither depends on the other
  1891.         return 0;
  1892.     }
  1893.  
  1894.     // }}}
  1895.     // {{{ _sortPkgDepsRev()
  1896.  
  1897.     /**
  1898.      * Compare two package's package.xml, and sort
  1899.      * so that dependencies are uninstalled last
  1900.      *
  1901.      * This is a crude compare, real dependency checking is done on uninstall.
  1902.      * The only purpose this serves is to make the command-line
  1903.      * order-independent (you can list a dependency first, and
  1904.      * uninstallation occurs in the order required)
  1905.      * @access private
  1906.      */
  1907.     function _sortPkgDepsRev($p1, $p2)
  1908.     {
  1909.         $p1name = $p1['info']['package'];
  1910.         $p2name = $p2['info']['package'];
  1911.         $p1deps = $this->_getRevPkgDeps($p1);
  1912.         $p2deps = $this->_getRevPkgDeps($p2);
  1913.         if (!count($p1deps) && !count($p2deps)) {
  1914.             return 0; // order makes no difference
  1915.         }
  1916.         if (!count($p1deps)) {
  1917.             return 1; // package 2 has dependencies, package 1 doesn't
  1918.         }
  1919.         if (!count($p2deps)) {
  1920.             return -1; // package 2 has dependencies, package 1 doesn't
  1921.         }
  1922.         // both have dependencies
  1923.         if (in_array($p1name, $p2deps)) {
  1924.             return 1; // put package 1 last
  1925.         }
  1926.         if (in_array($p2name, $p1deps)) {
  1927.             return -1; // put package 2 last
  1928.         }
  1929.         if ($this->_removedDependency($p1name, $p2name)) {
  1930.             return 1; // put package 1 last: package 2 depends on packages that depend on package 1
  1931.         }
  1932.         if ($this->_removedDependency($p2name, $p1name)) {
  1933.             return -1; // put package 2 last: package 1 depends on packages that depend on package 2
  1934.         }
  1935.         // doesn't really matter if neither depends on the other
  1936.         return 0;
  1937.     }
  1938.  
  1939.     // }}}
  1940.     // {{{ _getPkgDeps()
  1941.  
  1942.     /**
  1943.      * get an array of package dependency names
  1944.      * @param array
  1945.      * @return array
  1946.      * @access private
  1947.      */
  1948.     function _getPkgDeps($p)
  1949.     {
  1950.         if (!isset($p['info']['releases'])) {
  1951.             return $this->_getRevPkgDeps($p);
  1952.         }
  1953.         $rel = array_shift($p['info']['releases']);
  1954.         if (!isset($rel['deps'])) {
  1955.             return array();
  1956.         }
  1957.         $ret = array();
  1958.         foreach($rel['deps'] as $dep) {
  1959.             if ($dep['type'] == 'pkg') {
  1960.                 $ret[] = $dep['name'];
  1961.             }
  1962.         }
  1963.         return $ret;
  1964.     }
  1965.  
  1966.     // }}}
  1967.     // {{{ _getPkgDeps()
  1968.  
  1969.     /**
  1970.      * get an array representation of the package dependency tree
  1971.      * @return array
  1972.      * @access private
  1973.      */
  1974.     function _getPkgDepTree($packages)
  1975.     {
  1976.         $tree = array();
  1977.         foreach ($packages as $p) {
  1978.             $package = $p['info']['package'];
  1979.             $deps = $this->_getPkgDeps($p);
  1980.             $tree[$package] = $deps;
  1981.         }
  1982.         return $tree;
  1983.     }
  1984.  
  1985.     // }}}
  1986.     // {{{ _removedDependency($p1, $p2)
  1987.  
  1988.     /**
  1989.      * get an array of package dependency names for uninstall
  1990.      * @param string package 1 name
  1991.      * @param string package 2 name
  1992.      * @return bool
  1993.      * @access private
  1994.      */
  1995.     function _removedDependency($p1, $p2)
  1996.     {
  1997.         if (empty($this->_packageSortTree[$p2])) {
  1998.             return false;
  1999.         }
  2000.         if (!in_array($p1, $this->_packageSortTree[$p2])) {
  2001.             foreach ($this->_packageSortTree[$p2] as $potential) {
  2002.                 if ($this->_removedDependency($p1, $potential)) {
  2003.                     return true;
  2004.                 }
  2005.             }
  2006.             return false;
  2007.         }
  2008.         return true;
  2009.     }
  2010.  
  2011.     // }}}
  2012.     // {{{ _getRevPkgDeps()
  2013.  
  2014.     /**
  2015.      * get an array of package dependency names for uninstall
  2016.      * @param array
  2017.      * @return array
  2018.      * @access private
  2019.      */
  2020.     function _getRevPkgDeps($p)
  2021.     {
  2022.         if (!isset($p['info']['release_deps'])) {
  2023.             return array();
  2024.         }
  2025.         $ret = array();
  2026.         foreach($p['info']['release_deps'] as $dep) {
  2027.             if ($dep['type'] == 'pkg') {
  2028.                 $ret[] = $dep['name'];
  2029.             }
  2030.         }
  2031.         return $ret;
  2032.     }
  2033.  
  2034.     // }}}
  2035. }
  2036.  
  2037. ?>
  2038.